tg-me.com/csharp_1001_notes/685
Last Update:
⚙️ `[EnumeratorCancellation]` в C# — критически важный атрибут для корректной отмены в `IAsyncEnumerable`
В .NET асинхронные итераторы (`IAsyncEnumerable<T>`) стали мощным инструментом для потоковой обработки данных. Но если вы используете CancellationToken
без специального атрибута [EnumeratorCancellation]
, ваш код может вести себя некорректно и утекать ресурсы. Разберёмся подробно.
⚠️ Проблема: токен есть, но он бесполезен
Допустим, вы пишете такой метод:
public async IAsyncEnumerable<string> FetchItems(CancellationToken cancellationToken)
{
foreach (var id in ids)
{
var item = await GetDataAsync(id);
yield return item;
}
}
Выглядит нормально, но есть подвох: при отмене токен не передаётся в `MoveNextAsync()`, то есть итерация может продолжаться, даже если вызвавшая сторона уже вызвала
cancellationToken.Cancel()
.💣 Последствия
• Фоновая загрузка продолжается после отмены
• Зависшие соединения, неосвобождённые ресурсы
• Непредсказуемое поведение в
await foreach
• Сложные баги и плохая отзывчивость приложений
✅ Решение:
[EnumeratorCancellation]
Правильно будет вот так:
public async IAsyncEnumerable<string> FetchItems(
[EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var item in LoadFromDb().WithCancellation(cancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
yield return item;
}
}
📌 Атрибут
[EnumeratorCancellation]
сообщает компилятору, что токен должен быть передан в реализацию `MoveNextAsync()` итератора. Без этого атрибута токен проигнорируется.🧪 Как проверить
var cts = new CancellationTokenSource();
await foreach (var item in FetchItems(cts.Token))
{
if (item == "stop") cts.Cancel();
}
Если метод реализован без
[EnumeratorCancellation]
, цикл может не остановиться. Если с атрибутом — отмена сработает как положено, и итерация завершится немедленно.
🛠 Best Practices
✔ Всегда используйте
[EnumeratorCancellation]
, если метод IAsyncEnumerable<T>
принимает CancellationToken
✔ Внутри итератора:
- Вызывайте
ThrowIfCancellationRequested()
- Оборачивайте вложенные
await foreach
или асинхронные методы в .WithCancellation(token)
✔ Не используйте токен «для галочки» — он должен влиять на поведение итератора
✔ Добавляйте юнит‑тесты на отмену, особенно если вы работаете с I/O, API или базами данных
📎 Заключение
Асинхронные итераторы — мощь. Но без
[EnumeratorCancellation]
ваш токен отмены просто не работает. И это не очевидно, пока вы не столкнётесь с багом, когда ресурсы не освобождаются, или цикл не завершается.Одна строка — и вы защищены:
[EnumeratorCancellation] CancellationToken token
📚 Источник: https://bartwullems.blogspot.com/2025/04/asyncenumerable-in-c-importance-of.html
🧠 Если ты пишешь на C# и используешь
IAsyncEnumerable
— знай: токен без атрибута = фейковая отмена.📌 Читать
@csharp_1001_notes
BY C# 1001 notes
Warning: Undefined variable $i in /var/www/tg-me/post.php on line 283
Share with your friend now:
tg-me.com/csharp_1001_notes/685